---------------------
-- Robotic Panel   --
---------------------
-- A Cirr Creation --
---------------------
-- This file dictates what the behaviours a panel can carry out are,
-- what they involve and what is required. It also, more importantly,
-- allows a panel to make an intelligient guess as to what to do next
-- and whether to interrupt its current behaviour.

-- BEHAVIOUR FUNCTIONS

-- REPAIR
function Panels.Repair(panel, target)
  
  panel:SetNetworkedBool("drawBeam", false)
  
  local healthFull = false
  local energyFull = false
  
  if(panel.CurrHealth < panel.MaxHealth) then
    
    panel.CurrHealth = panel.CurrHealth + Panels.LIFE_ENERGY_COST
    
  else
    
    panel:SetMaterial("models/Maratix/panel_diffuse") -- hacky way to try to remove decals
    healthFull = true
    
  end
  
  if(panel.Energy < panel.MaxEnergy) then
    
    panel.Energy = panel.Energy + Panels.LIFE_ENERGY_COST
    
  else
    
    energyFull = true
    
  end
  
  panel:EmitSound(Panels.RepairBeamSound, 60, math.random(50, 150))
  
  if(healthFull && energyFull) then
    panel.CurrentAction = NULL -- We're done repairing, do something else!
  end
  
end

-- ATTACK
function Panels.Attack(panel, target)
  
  if(panel.Energy/panel.MaxEnergy < Panels.ENERGY_FLEE_RATIO) then -- We don't want to work ourselves to death now
  
    panel.MovementFunction = Panels.Movement.Flee
    panel.CurrentAction = Panels.Flee
    panel:SetNewColor(Panels.FLEE_COLOR)
    panel:SetNetworkedBool("drawBeam", false) -- No lasers if we're running!
    panel:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 70, math.random(50, 150))
    
  end
  
  local dist = panel:GetPos():Distance(target:GetPos())
  if(dist > Panels.ATTACK_MAX_DISTANCE + 10) then -- 10 is a fudge factor so it actually attacks
    panel:SetNetworkedBool("drawBeam", false)
    return
  end
  
  panel:SetNetworkedBool("drawBeam", true)
  
  if(target:Health() > 0) then
  
    panel.Energy = panel.Energy - (Panels.BEAM_ENERGY_COST * 0.2)
    target:TakeDamage(panel.BeamDamage * 0.2, panel, panel)
    panel:EmitSound(Panels.AttackBeamSound, 70, math.random(50, 150))
    
  else
  
    panel:SetNetworkedBool("drawBeam", false)
    panel.CurrentAction = NULL -- The target has died or disappeared so we need to do something else
    
  end
  
end

-- REVIVE
function Panels.Revive(panel, target)
  
  local dist = panel:GetPos():Distance(target:GetPos())
  if(dist > Panels.REVIVE_MAX_DISTANCE + 10) then
    panel:SetNetworkedBool("drawBeam", false)
    return
  end
  
  panel:SetNetworkedBool("drawBeam", true)
      
  if(target.Energy/target.MaxEnergy < Panels.ENERGY_REVIVE_RATIO && panel.Energy/panel.MaxEnergy > Panels.ENERGY_REVIVE_RATIO) then
    
    panel.Energy = panel.Energy - (Panels.REVIVE_ENERGY_COST * 0.2)
    target.Energy = target.Energy + (Panels.REVIVE_ENERGY_COST * 0.2)
    
  else
    
    if(target.Energy > 0) then
      target.Dormant = false
      target:GetPhysicsObject():SetVelocity(Vector(0, 0, 200))
    end
    
    panel:SetNetworkedBool("drawBeam", false)
    panel.CurrentAction = NULL -- The panel we've helped is up and about, do something else
    
  end
  
  panel:EmitSound(Panels.RepairBeamSound, 60, math.random(50, 150))
  
end

-- FLEE
function Panels.Flee(panel, target)
  
  panel:SetNetworkedBool("drawBeam", false)
  
  local dist = panel:GetPos():Distance(target:GetPos())

  if(dist > Panels.FLEE_MIN_DISTANCE) then
    panel.CurrentAction = NULL
  end
  
end

-- DECISION
-- And finally, a way to intelligiently choose between these behaviours
-- Or to override if necessary
-- This doesn't take into account overrides through communication
function Panels.DecideBehaviour(panel)
  
  -- No point doing anything if we're not able to do it, is there?
  if(panel.Dormant || !panel.IsAlive) then
    return NULL, Panels.Movement.Hover
  end
  
  -- Panels are first concerned with their own well-being.
  -- provided they aren't reviving
  
  
  local healthRatio = panel.CurrHealth/panel.MaxHealth
  local energyRatio = panel.Energy/panel.MaxEnergy
  if((healthRatio < Panels.HEALTH_REPAIR_RATIO || energyRatio < Panels.ENERGY_REPAIR_RATIO) && panel.CurrentAction != Panels.Revive) then
    
    if(panel.CurrentAction == Panels.Repair) then
      return Panels.Repair, Panels.Movement.Hover
    else
      panel.Target = panel -- This may seem pointless, but leaving the target unset means this function will be called again next cycle
      panel:SetNewColor(Panels.REPAIR_COLOR)
      panel:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(50, 150))
      return Panels.Repair, Panels.Movement.Hover
    end
    
  end
  
  -- Panels are then concerned about reviving their fallen comrades.
  local currPos = panel:GetPos()
  local target = NULL
  local closest = Panels.TARGET_MAX_DISTANCE
  local panels = ents.FindByClass("sent_panel")

  local dist
  
  for k,ent in pairs(panels) do
    
    if(ent != panel && ent.IsAlive && panel:Visible(ent) && ent.Dormant) then
      
      dist = ent:GetPos():Distance(currPos)
      if(dist < closest) then
        
        closest = dist
        target = ent
        
      end
      
    end
    
  end
  
  if(target:IsValid()) then
    
    if(panel.CurrentAction == Panels.Revive) then
      panel.Target = target
      panel:SetNetworkedEntity("targ", panel.Target)
      return Panels.Revive, Panels.Movement.Approach
    else
      panel.Target = target
      panel.TargetDistance = Panels.REVIVE_MAX_DISTANCE
      panel:SetNewColor(Panels.REVIVE_COLOR)
      panel:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(50, 150))
      panel:SetNetworkedEntity("targ", panel.Target)
      return Panels.Revive, Panels.Movement.Approach
    end
    
  end
  
  -- Panels then search for a new enemy.
  
  target = NULL
  closest = Panels.TARGET_MAX_DISTANCE
  local players = ents.FindByClass("player")
  local npcs = ents.FindByClass("npc_*")
  local nearbyEnts = table.Merge(players, npcs)

  local dist
  
  for k,ent in pairs(nearbyEnts) do
     
    if((ent:IsNPC() || (ent:IsPlayer() && ent:Alive())) && ent:Health() > 0 && panel:Visible(ent)) then
      
      dist = ent:GetPos():Distance(currPos)
      if(dist < closest) then
        
        closest = dist
        target = ent
        
      end
      
    end
    
  end
  
  if(target:IsValid()) then
    
    if(panel.CurrentAction == Panels.Attack) then
      panel.Target = target
      panel:SetNetworkedEntity("targ", panel.Target)
      return Panels.Attack, Panels.Movement.Approach
    else
      panel.Target = target
      panel.TargetDistance = Panels.ATTACK_MAX_DISTANCE
      panel:SetNewColor(Panels.ATTACK_COLOR)
      panel:EmitSound(Panels.DecisionSounds[math.random(1, #Panels.DecisionSounds)], 100, math.random(50, 150))
      panel:SetNetworkedEntity("targ", panel.Target)
      return Panels.Attack, Panels.Movement.Approach
    end
    
  end
  
  -- If there's nothing to do then just idle.
  
  panel:SetNewColor(Panels.IDLE_COLOR)
  return NULL, Panels.Movement.Hover
  
end
